
/**
 * Copyright (c) 2015 https://github.com/zhaohuatai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.zfes.snowy.core.util;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.zfes.snowy.core.exceptions.AppRuntimeException;


public class ZClassUtil extends ClassUtils{
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	private static final String RESOURCE_PATTERN = "/**/*.class";
	
	private static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
	@SafeVarargs
	public static Set<Class<?>> scanClass(String packagesToScan, Class<? extends Annotation>... annotationFilter){
		return scanClass(new String[]{packagesToScan},  annotationFilter);
	}
	@SafeVarargs
	public static Set<Class<?>> scanClass(String[]packagesToScan, Class<? extends Annotation>... annotationFilter){
		List<String> packagesList= initPackagesList(packagesToScan);
		List<TypeFilter> typeFilters = initTypeFilters(packagesToScan,  annotationFilter);
		Set<Class<?>> classSet= new LinkedHashSet<Class<?>>();
		try {
			classSet= getClassSet( packagesList, typeFilters);
		} catch (Exception e) {
			throw new AppRuntimeException(e.getMessage());
		}
		return classSet;
	}
	
	private static Set<Class<?>> getClassSet(List<String> packagesList,List<TypeFilter> typeFilters) throws IOException, ClassNotFoundException {
		Set<Class<?>> classSet= new LinkedHashSet<Class<?>>();
		if (!packagesList.isEmpty()) {
				for (String pkg : packagesList) {
					String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
							ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
					Resource[] resources = resourcePatternResolver.getResources(pattern);
					MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
					for (Resource resource : resources) {
						if (resource.isReadable()) {
							MetadataReader reader = readerFactory.getMetadataReader(resource);
							String className = reader.getClassMetadata().getClassName();
							if (matchesEntityTypeFilter(reader, readerFactory,typeFilters)) {
								classSet.add(Class.forName(className));
							}
						}
					}
				}
		}
		return classSet;
	}
	private static List<String> initPackagesList(String[] packagesToScan){
		List<String> packagesList= new LinkedList<String>();
		if (packagesToScan != null) {
			for (String packagePath : packagesToScan) {
				packagesList.add(packagePath);
			}
		}
		return packagesList;
	}
	@SafeVarargs
	private static List<TypeFilter> initTypeFilters(String[] packagesToScan, Class<? extends Annotation>... annotationFilter){
		List<TypeFilter> typeFilters= new LinkedList<TypeFilter>();
		if (annotationFilter != null){
			for (Class<? extends Annotation> annotation : annotationFilter) {
				typeFilters.add(new AnnotationTypeFilter(annotation, false));
			}
		}
		return typeFilters;
	}
	/**
	 * 检查当前扫描到的Bean含有任何一个指定的注解标记
	 * @param reader
	 * @param readerFactory
	 * @return
	 * @throws IOException
	 */
	private static boolean  matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory,List<TypeFilter> typeFilters) throws IOException {
		if (!typeFilters.isEmpty()) {
			for (TypeFilter filter : typeFilters) {
				if (filter.match(reader, readerFactory)) {
					return true;
				}
			}
		}
		return false;
	}

}